package lelouet.sh3d;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import com.eteks.sweethome3d.io.DefaultFurnitureCatalog;
import com.eteks.sweethome3d.io.DefaultFurnitureCatalog.PropertyKey;
import com.eteks.sweethome3d.model.CatalogDoorOrWindow;
import com.eteks.sweethome3d.model.CatalogLight;
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
import com.eteks.sweethome3d.model.Content;
import com.eteks.sweethome3d.model.FurnitureCatalog;
import com.eteks.sweethome3d.model.FurnitureCategory;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomePieceOfFurniture;
import com.eteks.sweethome3d.model.LightSource;
import com.eteks.sweethome3d.model.Sash;
import com.eteks.sweethome3d.model.Wall;
import com.eteks.sweethome3d.plugin.Plugin;
import com.eteks.sweethome3d.tools.ResourceURLContent;
import com.eteks.sweethome3d.tools.URLContent;

public class Utils {

	public static void clearHomeFurniture(Home home) {
		List<HomePieceOfFurniture> toempty = new ArrayList<HomePieceOfFurniture>();
		for (HomePieceOfFurniture hpof : home.getFurniture()) {
			toempty.add(hpof);
		}
		for (HomePieceOfFurniture hpof : toempty) {
			home.deletePieceOfFurniture(hpof);
		}
	}

	public static void emptyCatalog(FurnitureCatalog catalog) {
		List<CatalogPieceOfFurniture> toDel = new ArrayList<CatalogPieceOfFurniture>();
		for (FurnitureCategory fc : catalog.getCategories()) {
			for (CatalogPieceOfFurniture cpof : fc.getFurniture()) {
				toDel.add(cpof);
			}
		}
		for (CatalogPieceOfFurniture cpof : toDel) {
			catalog.delete(cpof);
		}
	}

	public static boolean isVoidContent(Content content) {
		if (content == null) {
			return true;
		}
		try {
			InputStream stream = content.openStream();
			if (stream.read() < 0) {
				return true;
			}
			stream.close();
		} catch (IOException e) {
			e.printStackTrace();
			return true;
		}

		return false;
	}

	/** @return a resource inside a jar specified by its classloader */
	public static Content getContentInJar(ClassLoader classloader,
			String contentFile) {
		try {
			if (!contentFile.startsWith("/")) {
				contentFile = '/' + contentFile;
			}
			URL fullURL = new URL("jar:"
					+ ((URLClassLoader) classloader).getURLs()[0] + "!"
					+ contentFile);
			Content ret = new ResourceURLContent(fullURL, true);
			if (isVoidContent(ret)) {
				System.err.println("  the content is void");
			}
			return ret;
		} catch (MalformedURLException e) {
			System.err.println("while loading " + contentFile
					+ "\n  classloader URL : "
					+ ((URLClassLoader) classloader).getURLs()[0]);
			e.printStackTrace(System.err);
			return null;
		}
	}

	/**
	 * Returns a valid content instance from the resource file or URL value of
	 * key.
	 * 
	 * @param resource
	 *            a resource bundle
	 * @param contentKey
	 *            the key of a resource content file
	 * @param furnitureUrl
	 *            the URL of the file containing the target resource if it's not
	 *            <code>null</code>
	 * @param multiPartModel
	 *            if <code>true</code> the resource is a multi part resource
	 *            stored in a directory with other required resources
	 * @throws IllegalArgumentException
	 *             if the file value doesn't match a valid resource or URL.
	 */
	public static Content getContent(ResourceBundle resource,
			String contentKey, URL furnitureUrl, boolean multiPartModel,
			boolean optional) {
		String contentFile = optional ? getOptionalString(resource, contentKey,
				null) : resource.getString(contentKey);
		if (optional && contentFile == null) {
			return null;
		}
		try {
			// Try first to interpret contentFile as a URL
			return new URLContent(new URL(contentFile));
		} catch (MalformedURLException ex) {
			if (furnitureUrl == null) {
				// Otherwise find if it's a resource
				return new ResourceURLContent(DefaultFurnitureCatalog.class,
						contentFile, multiPartModel);
			} else {
				try {
					URL contentUrl = new URL("jar:" + furnitureUrl + "!"
							+ contentFile);
					return new ResourceURLContent(contentUrl, multiPartModel);
				} catch (MalformedURLException ex2) {
					if (optional) {
						return null;
					}
					throw new IllegalArgumentException("Invalid URL", ex2);
				}
			}
		}
	}

	/**
	 * Returns optional door or windows sashes.
	 */
	public static Sash[] getDoorOrWindowSashes(ResourceBundle resource,
			int index, float doorOrWindowWidth, float doorOrWindowDepth)
			throws MissingResourceException {
		Sash[] sashes;
		String sashXAxisString = getOptionalString(resource,
				PropertyKey.DOOR_OR_WINDOW_SASH_X_AXIS.getKey(index), null);
		if (sashXAxisString != null) {
			String[] sashXAxisValues = sashXAxisString.split(" ");
			// If doorOrWindowHingesX#i key exists the 3 other keys with the
			// same count of numbers must exist too
			String[] sashYAxisValues = resource.getString(
					PropertyKey.DOOR_OR_WINDOW_SASH_Y_AXIS.getKey(index))
					.split(" ");
			if (sashYAxisValues.length != sashXAxisValues.length) {
				throw new IllegalArgumentException("Expected "
						+ sashXAxisValues.length + " values in "
						+ PropertyKey.DOOR_OR_WINDOW_SASH_Y_AXIS.getKey(index)
						+ " key");
			}
			String[] sashWidths = resource.getString(
					PropertyKey.DOOR_OR_WINDOW_SASH_WIDTH.getKey(index)).split(
					" ");
			if (sashWidths.length != sashXAxisValues.length) {
				throw new IllegalArgumentException("Expected "
						+ sashXAxisValues.length + " values in "
						+ PropertyKey.DOOR_OR_WINDOW_SASH_WIDTH.getKey(index)
						+ " key");
			}
			String[] sashStartAngles = resource.getString(
					PropertyKey.DOOR_OR_WINDOW_SASH_START_ANGLE.getKey(index))
					.split(" ");
			if (sashStartAngles.length != sashXAxisValues.length) {
				throw new IllegalArgumentException("Expected "
						+ sashXAxisValues.length
						+ " values in "
						+ PropertyKey.DOOR_OR_WINDOW_SASH_START_ANGLE
								.getKey(index) + " key");
			}
			String[] sashEndAngles = resource.getString(
					PropertyKey.DOOR_OR_WINDOW_SASH_END_ANGLE.getKey(index))
					.split(" ");
			if (sashEndAngles.length != sashXAxisValues.length) {
				throw new IllegalArgumentException("Expected "
						+ sashXAxisValues.length
						+ " values in "
						+ PropertyKey.DOOR_OR_WINDOW_SASH_END_ANGLE
								.getKey(index) + " key");
			}

			sashes = new Sash[sashXAxisValues.length];
			for (int i = 0; i < sashes.length; i++) {
				// Create the matching sash, converting cm to percentage of
				// width or depth, and degrees to radians
				sashes[i] = new Sash(Float.parseFloat(sashXAxisValues[i])
						/ doorOrWindowWidth, Float
						.parseFloat(sashYAxisValues[i])
						/ doorOrWindowDepth, Float.parseFloat(sashWidths[i])
						/ doorOrWindowWidth, (float) Math.toRadians(Float
						.parseFloat(sashStartAngles[i])), (float) Math
						.toRadians(Float.parseFloat(sashEndAngles[i])));
			}
		} else {
			sashes = new Sash[0];
		}

		return sashes;
	}

	/**
	 * @param catalog
	 *            the catalog to search the furniture in
	 * @param id
	 *            the id of the furniture we require
	 * @return a furniture in that catalog with given id, or null
	 */
	public static CatalogPieceOfFurniture getFurnitureFromId(
			FurnitureCatalog catalog, String id) {
		for (FurnitureCategory fc : catalog.getCategories()) {
			for (CatalogPieceOfFurniture cpof : fc.getFurniture()) {
				if (cpof.getId() != null && cpof.getId().equals(id)) {
					return cpof;
				}
			}
		}
		return null;
	}

	public static String getDefaultServerId(int vSize) {
		return "default" + vSize + "U";
	}

	/**
	 * Returns optional light sources.
	 */
	public static LightSource[] getLightSources(ResourceBundle resource,
			int index, float lightWidth, float lightDepth, float lightHeight)
			throws MissingResourceException {
		LightSource[] lightSources = null;
		String lightSourceXString = getOptionalString(resource,
				PropertyKey.LIGHT_SOURCE_X.getKey(index), null);
		if (lightSourceXString != null) {
			String[] lightSourceX = lightSourceXString.split(" ");
			// If doorOrWindowHingesX#i key exists the 3 other keys with the
			// same count of numbers must exist too
			String[] lightSourceY = resource.getString(
					PropertyKey.LIGHT_SOURCE_Y.getKey(index)).split(" ");
			if (lightSourceY.length != lightSourceX.length) {
				throw new IllegalArgumentException("Expected "
						+ lightSourceX.length + " values in "
						+ PropertyKey.LIGHT_SOURCE_Y.getKey(index) + " key");
			}
			String[] lightSourceZ = resource.getString(
					PropertyKey.LIGHT_SOURCE_Z.getKey(index)).split(" ");
			if (lightSourceZ.length != lightSourceX.length) {
				throw new IllegalArgumentException("Expected "
						+ lightSourceX.length + " values in "
						+ PropertyKey.LIGHT_SOURCE_Z.getKey(index) + " key");
			}
			String[] lightSourceColors = resource.getString(
					PropertyKey.LIGHT_SOURCE_COLOR.getKey(index)).split(" ");
			if (lightSourceColors.length != lightSourceX.length) {
				throw new IllegalArgumentException("Expected "
						+ lightSourceX.length + " values in "
						+ PropertyKey.LIGHT_SOURCE_COLOR.getKey(index) + " key");
			}

			lightSources = new LightSource[lightSourceX.length];
			for (int i = 0; i < lightSources.length; i++) {
				int color = lightSourceColors[i].startsWith("#") ? Integer
						.parseInt(lightSourceColors[i].substring(1), 16)
						: Integer.parseInt(lightSourceColors[i]);
				// Create the matching light source, converting cm to percentage
				// of width, depth and height
				lightSources[i] = new LightSource(Float
						.parseFloat(lightSourceX[i])
						/ lightWidth, Float.parseFloat(lightSourceY[i])
						/ lightDepth, Float.parseFloat(lightSourceZ[i])
						/ lightHeight, color);
			}
		}
		return lightSources;
	}

	// /**
	// * Returns the piece of furniture at the given <code>index</code> of a
	// * localized <code>resource</code> bundle.
	// *
	// * @param resource
	// * a resource bundle
	// * @param index
	// * the index of the read piece
	// * @param elementContainer
	// * {@link ClassPath} from which piece resources will be loaded
	// * @return the read piece of furniture or <code>null</code> if the piece
	// at
	// * the given index doesn't exist.
	// * @throws MissingResourceException
	// * if mandatory keys are not defined.
	// */
	// public static CatalogPieceOfFurniture readPieceOfFurniture(
	// ResourceBundle resource, int index, ClassLoader elementContainer) {
	// String name = null;
	// try {
	// name = resource.getString(PropertyKey.NAME.getKey(index));
	// } catch (MissingResourceException ex) {
	// // Return null if key name# doesn't exist
	// return null;
	// }
	// String description = getOptionalString(resource,
	// PropertyKey.DESCRIPTION.getKey(index), null);
	// Content icon = getContent(resource, PropertyKey.ICON.getKey(index),
	// elementContainer, false, true);
	// Content planIcon = getContent(resource, PropertyKey.PLAN_ICON
	// .getKey(index), elementContainer, false, true);
	// boolean multiPartModel = false;
	// try {
	// multiPartModel = Boolean.parseBoolean(resource
	// .getString(PropertyKey.MULTI_PART_MODEL.getKey(index)));
	// } catch (MissingResourceException ex) {
	// // By default inDirectory is false
	// }
	// Content model = getContent(resource, PropertyKey.MODEL.getKey(index),
	// elementContainer, multiPartModel, false);
	// float width = Float.parseFloat(resource.getString(PropertyKey.WIDTH
	// .getKey(index)));
	// float depth = Float.parseFloat(resource.getString(PropertyKey.DEPTH
	// .getKey(index)));
	// float height = Float.parseFloat(resource.getString(PropertyKey.HEIGHT
	// .getKey(index)));
	// boolean movable = Boolean.parseBoolean(resource
	// .getString(PropertyKey.MOVABLE.getKey(index)));
	// boolean doorOrWindow = Boolean.parseBoolean(resource
	// .getString(PropertyKey.DOOR_OR_WINDOW.getKey(index)));
	// float elevation = getOptionalFloat(resource, PropertyKey.ELEVATION
	// .getKey(index), 0);
	// float[][] modelRotation = getModelRotation(resource,
	// PropertyKey.MODEL_ROTATION.getKey(index));
	// // By default creator is eTeks
	// String creator = getOptionalString(resource, PropertyKey.CREATOR
	// .getKey(index), "eTeks");
	// String id = getOptionalString(resource, PropertyKey.ID.getKey(index),
	// null);
	// boolean resizable = true;
	// try {
	// resizable = Boolean.parseBoolean(resource
	// .getString(PropertyKey.RESIZABLE.getKey(index)));
	// } catch (MissingResourceException ex) {
	// // By default piece is resizable
	// }
	// BigDecimal price = null;
	// try {
	// price = new BigDecimal(resource.getString(PropertyKey.PRICE
	// .getKey(index)));
	// } catch (MissingResourceException ex) {
	// // By default price is null
	// }
	// BigDecimal valueAddedTaxPercentage = null;
	// try {
	// valueAddedTaxPercentage = new BigDecimal(resource
	// .getString(PropertyKey.VALUE_ADDED_TAX_PERCENTAGE
	// .getKey(index)));
	// } catch (MissingResourceException ex) {
	// // By default price is null
	// }
	//
	// if (doorOrWindow) {
	// float wallThicknessPercentage = getOptionalFloat(resource,
	// PropertyKey.DOOR_OR_WINDOW_WALL_THICKNESS.getKey(index),
	// depth)
	// / depth;
	// float wallDistancePercentage = getOptionalFloat(resource,
	// PropertyKey.DOOR_OR_WINDOW_WALL_DISTANCE.getKey(index), 0)
	// / depth;
	// Sash[] sashes = getDoorOrWindowSashes(resource, index, width, depth);
	// return new CatalogDoorOrWindow(id, name, description, icon,
	// planIcon, model, width, depth, height, elevation, movable,
	// wallThicknessPercentage, wallDistancePercentage, sashes,
	// modelRotation, creator, resizable, price,
	// valueAddedTaxPercentage);
	// } else {
	// LightSource[] lightSources = getLightSources(resource, index,
	// width, depth, height);
	// if (lightSources != null) {
	// return new CatalogLight(id, name, description, icon, planIcon,
	// model, width, depth, height, elevation, movable,
	// lightSources, modelRotation, creator, resizable, price,
	// valueAddedTaxPercentage);
	// } else {
	// return new CatalogPieceOfFurniture(id, name, description, icon,
	// planIcon, model, width, depth, height, elevation,
	// movable, modelRotation, creator, resizable, price,
	// valueAddedTaxPercentage);
	// }
	// }
	// }

	/**
	 * Returns model rotation parsed from key value.
	 */
	public static float[][] getModelRotation(ResourceBundle resource, String key) {
		if (!resource.containsKey(key)) {
			return null;
		}
		try {
			String modelRotationString = resource.getString(key);
			String[] values = modelRotationString.split(" ", 9);

			if (values.length == 9) {
				return new float[][] {
						{ Float.parseFloat(values[0]),
								Float.parseFloat(values[1]),
								Float.parseFloat(values[2]) },
						{ Float.parseFloat(values[3]),
								Float.parseFloat(values[4]),
								Float.parseFloat(values[5]) },
						{ Float.parseFloat(values[6]),
								Float.parseFloat(values[7]),
								Float.parseFloat(values[8]) } };
			} else {
				return null;
			}
		} catch (NumberFormatException ex) {
			System.err.println("error while parsing the rotation for string "
					+ key + " in " + resource + " ; exception is " + ex);
			return null;
		}
	}

	/**
	 * Returns the value of <code>propertyKey</code> in <code>resource</code>,
	 * or <code>defaultValue</code> if the property doesn't exist.
	 */
	public static float getOptionalFloat(ResourceBundle resource,
			String propertyKey, float defaultValue) {
		if (resource.containsKey(propertyKey)) {
			return Float.parseFloat(resource.getString(propertyKey));
		}
		return defaultValue;
	}

	/**
	 * Returns the value of <code>propertyKey</code> in <code>resource</code>,
	 * or <code>defaultValue</code> if the property doesn't exist.
	 */
	public static String getOptionalString(ResourceBundle resource,
			String propertyKey, String defaultValue) {
		if (resource.containsKey(propertyKey)) {
			return resource.getString(propertyKey);
		}
		return defaultValue;
	}

	public static void loadServerCatalog(String bundleFileName,
			URL baseResourceURL, FurnitureCatalog inCatalog) {
		FurnitureBundleParser fbi = new FurnitureBundleParser(bundleFileName,
				baseResourceURL);
		for (CatalogPieceOfFurniture cpof : fbi) {
			inCatalog
					.add(new FurnitureCategory(fbi.categories.get(cpof)), cpof);
		}
	}

	/**
	 * Returns the furniture category of a piece at the given <code>index</code>
	 * of a localized <code>resource</code> bundle.
	 * 
	 * @throws MissingResourceException
	 *             if mandatory keys are not defined.
	 */
	public static FurnitureCategory readFurnitureCategory(
			ResourceBundle resource, int index) {
		String category = resource
				.getString(PropertyKey.CATEGORY.getKey(index));
		return new FurnitureCategory(category);
	}

	/**
	 * load a content specified by its name, declared in a plugin (usually in
	 * its jar)
	 */
	public Content loadContentInPlugin(Plugin plugin, String fileName) {
		final URL jarURL = ((URLClassLoader) plugin.getPluginClassLoader())
				.getURLs()[0];
		try {
			return new ResourceURLContent(new URL("jar:" + jarURL + "!"
					+ fileName), false);
		} catch (MalformedURLException e) {
			e.printStackTrace(System.err);
			return null;
		}
	}

	/**
	 * Returns the piece of furniture at the given <code>index</code> of a
	 * localized <code>resource</code> bundle.
	 * 
	 * @param resource
	 *            a resource bundle
	 * @param index
	 *            the index of the read piece
	 * @param furnitureCatalogUrl
	 *            the URL from which piece resources will be loaded or
	 *            <code>null</code> if it's read from current classpath.
	 * @return the read piece of furniture or <code>null</code> if the piece at
	 *         the given index doesn't exist.
	 * @throws MissingResourceException
	 *             if mandatory keys are not defined.
	 */
	public static CatalogPieceOfFurniture readPieceOfFurniture(
			ResourceBundle resource, int index, URL furnitureCatalogUrl,
			Map<CatalogPieceOfFurniture, String> categories) {
		String name = null;
		try {
			name = resource.getString(PropertyKey.NAME.getKey(index));
		} catch (MissingResourceException ex) {
			// Return null if key name# doesn't exist
			return null;
		}
		String description = getOptionalString(resource,
				PropertyKey.DESCRIPTION.getKey(index), null);
		String category = getOptionalString(resource, PropertyKey.CATEGORY
				.getKey(index), null);
		Content icon = getContent(resource, PropertyKey.ICON.getKey(index),
				furnitureCatalogUrl, false, false);
		Content planIcon = getContent(resource, PropertyKey.PLAN_ICON
				.getKey(index), furnitureCatalogUrl, false, true);
		boolean multiPartModel = false;
		try {
			multiPartModel = Boolean.parseBoolean(resource
					.getString(PropertyKey.MULTI_PART_MODEL.getKey(index)));
		} catch (MissingResourceException ex) {
			// By default inDirectory is false
		}
		Content model = getContent(resource, PropertyKey.MODEL.getKey(index),
				furnitureCatalogUrl, multiPartModel, false);
		float width = Float.parseFloat(resource.getString(PropertyKey.WIDTH
				.getKey(index)));
		float depth = Float.parseFloat(resource.getString(PropertyKey.DEPTH
				.getKey(index)));
		float height = Float.parseFloat(resource.getString(PropertyKey.HEIGHT
				.getKey(index)));
		boolean movable = Boolean.parseBoolean(resource
				.getString(PropertyKey.MOVABLE.getKey(index)));
		boolean doorOrWindow = Boolean.parseBoolean(resource
				.getString(PropertyKey.DOOR_OR_WINDOW.getKey(index)));
		float elevation = getOptionalFloat(resource, PropertyKey.ELEVATION
				.getKey(index), 0);
		float[][] modelRotation = getModelRotation(resource,
				PropertyKey.MODEL_ROTATION.getKey(index));
		// By default creator is eTeks
		String creator = getOptionalString(resource, PropertyKey.CREATOR
				.getKey(index), "eTeks");
		String id = getOptionalString(resource, PropertyKey.ID.getKey(index),
				null);
		boolean resizable = true;
		try {
			resizable = Boolean.parseBoolean(resource
					.getString(PropertyKey.RESIZABLE.getKey(index)));
		} catch (MissingResourceException ex) {
			// By default piece is resizable
		}
		BigDecimal price = null;
		try {
			price = new BigDecimal(resource.getString(PropertyKey.PRICE
					.getKey(index)));
		} catch (MissingResourceException ex) {
			// By default price is null
		}
		BigDecimal valueAddedTaxPercentage = null;
		try {
			valueAddedTaxPercentage = new BigDecimal(resource
					.getString(PropertyKey.VALUE_ADDED_TAX_PERCENTAGE
							.getKey(index)));
		} catch (MissingResourceException ex) {
			// By default price is null
		}

		if (doorOrWindow) {
			float wallThicknessPercentage = getOptionalFloat(resource,
					PropertyKey.DOOR_OR_WINDOW_WALL_THICKNESS.getKey(index),
					depth)
					/ depth;
			float wallDistancePercentage = getOptionalFloat(resource,
					PropertyKey.DOOR_OR_WINDOW_WALL_DISTANCE.getKey(index), 0)
					/ depth;
			Sash[] sashes = getDoorOrWindowSashes(resource, index, width, depth);
			return new CatalogDoorOrWindow(id, name, description, icon,
					planIcon, model, width, depth, height, elevation, movable,
					wallThicknessPercentage, wallDistancePercentage, sashes,
					modelRotation, creator, resizable, price,
					valueAddedTaxPercentage);
		} else {
			LightSource[] lightSources = getLightSources(resource, index,
					width, depth, height);
			if (lightSources != null) {
				return new CatalogLight(id, name, description, icon, planIcon,
						model, width, depth, height, elevation, movable,
						lightSources, modelRotation, creator, resizable, price,
						valueAddedTaxPercentage);
			} else {
				CatalogPieceOfFurniture ret = new CatalogPieceOfFurniture(id,
						name, description, icon, planIcon, model, width, depth,
						height, elevation, movable, modelRotation, creator,
						resizable, price, valueAddedTaxPercentage);
				categories.put(ret, category);
				return ret;
			}
		}
	}

	public static void clearWalls(Home home) {
		ArrayList<Wall> walls = new ArrayList<Wall>();
		walls.addAll(home.getWalls());
		for (Wall wall : walls) {
			home.deleteWall(wall);
		}
	}

	public static void removeCategory(FurnitureCatalog catalog, String category) {
		List<CatalogPieceOfFurniture> toDel = new ArrayList<CatalogPieceOfFurniture>();
		for (FurnitureCategory fc : catalog.getCategories()) {
			if (fc.getName().equals(category)) {
				for (CatalogPieceOfFurniture cpof : fc.getFurniture()) {
					toDel.add(cpof);
				}
			}
		}
		for (CatalogPieceOfFurniture cpof : toDel) {
			catalog.delete(cpof);
		}
	}
}
